package com.briup.pai.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.briup.pai.common.constant.OperatorConstant;
import com.briup.pai.common.enums.OperatorCategoryEnum;
import com.briup.pai.common.enums.ResultCodeEnum;
import com.briup.pai.common.exception.BriupAssert;
import com.briup.pai.common.exception.CustomException;
import com.briup.pai.convert.OperatorConvert;
import com.briup.pai.dao.OperatorMapper;
import com.briup.pai.entity.dto.OperatorImportDTO;
import com.briup.pai.entity.dto.OperatorUpdateDTO;
import com.briup.pai.entity.po.Operator;
import com.briup.pai.entity.vo.DropDownVO;
import com.briup.pai.entity.vo.OperatorEchoVO;
import com.briup.pai.entity.vo.OperatorPageVO;
import com.briup.pai.entity.vo.PageVO;
import com.briup.pai.service.IOperatorService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
// 添加缓存前缀
@CacheConfig(cacheNames = OperatorConstant.OPERATOR_CACHE_PREFIX)
public class OperatorServiceImpl extends ServiceImpl<OperatorMapper, Operator> implements IOperatorService {

    @Autowired
    private OperatorConvert operatorConvert;


    @Override
    @CachePut(key = "T(com.briup.pai.common.constant.CommonConstant).DROPDOWN_CACHE_PREFIX")
    public void importOperator(MultipartFile file) {
        // 判断文件类型是不是excel
        BriupAssert.requireExcel(file);

        // 需要过滤掉数据库中算⼦名称已经存在的算⼦数据
        // 查询出数据库中已经存在的算子名称
        List<String> existsOperatorNames = new ArrayList<>(
                this.baseMapper.selectList(null)
                .stream()
                .map(Operator::getOperatorName)
                .toList());
        // 读取excel文件的内容，并做数据的转换AnalysisEventListener
        // 准备一个数据的列表，用来做数据库入库
        List<OperatorImportDTO> list = new ArrayList<>();
        try {
            EasyExcel.read(file.getInputStream(),
                    OperatorImportDTO.class,
                    new AnalysisEventListener<OperatorImportDTO>() {

                        @Override
                        public void invoke(OperatorImportDTO operatorImportDTO, AnalysisContext analysisContext) {
                            // 执行一行时验证算子的名称是不是在existsOperatorNames里
                            if(!existsOperatorNames.contains(operatorImportDTO.getOperatorName())){
                                // 添加数据
                                list.add(operatorImportDTO);
                            }
                            existsOperatorNames.add(operatorImportDTO.getOperatorName());
                        }

                        @Override
                        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                            // 执行完毕之后，筛选数据，存入数据库中
                            // 类型转换
                            List<Operator> operatorList = operatorConvert.operatorImportDto2PoList(list)
                                    .stream()
                                    .filter(s -> s.getOperatorType() != -1 && s.getOperatorCategory() != -1)
                                    .toList();
                            // 使用aop获取当前外部类的代理类对象
                            IOperatorService operatorService = (IOperatorService) AopContext.currentProxy();
                            operatorService.saveBatch(operatorList);
                        }
                    }).sheet(0).doRead();
        }catch (Exception e){
             throw new CustomException(ResultCodeEnum.FILE_IMPORT_ERROR);
        }
        System.out.println("导入算子数据成功！");
    }

    @Override
    public PageVO<OperatorPageVO> getOperatorByPageAndCondition(Long pageNum, Long pageSize, Integer operatorType, Integer operatorCategory) {

        // 定义分页
        IPage<Operator> page =  new Page<>(pageNum,pageSize);
        // 查询
        LambdaQueryWrapper<Operator> lqw = new LambdaQueryWrapper<>();
        lqw.eq(ObjectUtil.isNotNull(operatorType),Operator::getOperatorType,operatorType)
                .eq(ObjectUtil.isNotNull(operatorCategory),Operator::getOperatorCategory,operatorCategory);
        List<Operator> operators = this.baseMapper.selectList(page, lqw);
        List<OperatorPageVO> operatorPageVOS = operatorConvert.po2OperatorPageVOList(operators);
        PageVO<OperatorPageVO> result = new  PageVO<>();
        result.setTotal(page.getTotal());
        result.setData(operatorPageVOS);
        return result;
    }

    @Override
    @Cacheable(key = "operatorId")
    public OperatorEchoVO getOperatorById(Integer operatorId) {
        // 验证该id下算子是否存在
        return operatorConvert.po2OperatorEchoVO(
                BriupAssert.requireNotNull(
                this,
                Operator::getId,
                operatorId,
                ResultCodeEnum.DATA_NOT_EXIST));
    }

    @Override
    @CachePut(key = "#dto.getOperatorId()")
    @CacheEvict(key = "T(com.briup.pai.common.constant.CommonConstant).DROPDOWN_CACHE_PREFIX")
    public OperatorEchoVO modifyOperatorById(OperatorUpdateDTO dto) {
        // 算子必须存在
        BriupAssert.requireNotNull(this,
                Operator::getId,
                dto.getOperatorId(),
                ResultCodeEnum.DATA_NOT_EXIST);
        // 算子的名称和路径不能重复
        BriupAssert.requireNull(this,
                Operator::getOperatorName,dto.getOperatorName(),
                Operator::getOperatorUrl,dto.getOperatorUrl(),
                ResultCodeEnum.DATA_ALREADY_EXIST);
        // 类型转换
        Operator operator = operatorConvert.operatorUpdateDTO2po(dto);
        this.updateById(operator);
        return operatorConvert.po2OperatorEchoVO(operator);
    }

    @Override
    @Caching(evict = {
            @CacheEvict(key = "#operatorId"),
            @CacheEvict(key = "T(com.briup.pai.common.constant.CommonConstant).DROPDOWN_CACHE_PREFIX")
    })
    public void removeOperatorById(Integer operatorId) {
        BriupAssert.requireNotNull(this,
                Operator::getId,
                operatorId,
                ResultCodeEnum.DATA_NOT_EXIST);
        this.removeById(operatorId);
    }

    @Override
    @Transactional
    @CacheEvict
    public void removeOperatorByIds(List<Integer> ids) {
        this.removeBatchByIds(ids);
    }

    @Override
    @Cacheable(key = "T(com.briup.pai.common.constant.CommonConstant).DROPDOWN_CACHE_PREFIX")
    public Map<Integer, List<DropDownVO>> getOperatorDropDownList() {

        // 准备一个map用来返回
        Map<Integer,List<DropDownVO>> map = new HashMap<>();
        // 按照category遍历并且进行查询
        OperatorCategoryEnum.categoryList().forEach(categoryId->{
            // 根据categoryId查询该类别下算子的名称 = 查出来对应的算子对象
            LambdaQueryWrapper<Operator> lqw = new LambdaQueryWrapper<>();
            lqw.eq(Operator::getOperatorCategory,categoryId);
            List<DropDownVO> dropDownVOS = operatorConvert.po2DropDownList(this.list(lqw));
            map.put(categoryId,dropDownVOS);
        });
        return map;
    }
}